package core
{
	import com.adobe.serialization.json.JSON;
	
	import core.events.PutIoErrorEvent;
	import core.events.PutIoEvent;
	import core.events.PutIoResultEvent;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLRequestHeader;
	import flash.net.URLRequestMethod;
	import flash.net.URLVariables;
	import flash.system.ApplicationDomain;
	import flash.system.LoaderContext;
	import flash.system.Security;
	
	import tools.PutIoLibSettings;

	/**
	 * This is the core class to communicate with put.io's server API
	 * 
	 * The communication between put.io and this class is in JSON by using HTTP GET requests
	 * 
	 * All calls are async and callbacks are made with events
	 * 
	 * @see PutIoErrorEvent
	 * @see PutIoEvent
	 * @see PutIoResultEvent
	 */	
	public class PutIoLoader
	{
		private static const PUT_DOT_IO_API_URL:String = 'http://api.put.io/v1/';
		private static const END_URL:String = '?method=';
		
		private static const USE_WEAK_REFERENCES:Boolean = true;
		
		private var _id:String;
		
		private var url:String;		
		private var request:URLRequest;
		private var loader:URLLoader;
		private var parameters:URLVariables;
		
		private var eventDispatcher:EventDispatcher = new EventDispatcher();
		
		/**
		 * Initiating a Put.io request
		 * 
		 * @param category Class to call (ex: files, messages, transfers, urls, user, subscription)
		 * @param method Class method to call (ex: list, add, cancel for transfers category)
		 * @param parameters Parameters to send
		 */
		public function PutIoLoader(category:String, method:String, parameters:Object = null)
		{			
			this.url = getUrl(category, method);
									
			this.parameters = getParameters(parameters);
			
			this.request = getUrlRequestInstance(this.url);
			this.request.data = '&'+this.parameters;
			
			this.loader = getUrlLoaderInstance();
		}

		/**
		 * Send the request to Put.io
		 */		
		public function load():void
		{			
			this.loader.load(this.request);	
		}
		
		/**
		 * Cancel the sending request
		 */		
		public function cancel():void
		{
			try {
				this.loader.close();	
			} catch(error:Error){
			}
		}
		
		public function isComplete():Boolean
		{
			return (this.loader.bytesLoaded == this.loader.bytesTotal);
		}
		
		public function getPercentLoaded():int
		{
			return Math.round((this.loader.bytesLoaded/this.loader.bytesTotal)*100);
		}
		
		public function addEventListeners(result:Function, error:Function = null):void
		{
			this.eventDispatcher.addEventListener(PutIoEvent.PUT_IO_RESULT_EVENT, result, false, 0, USE_WEAK_REFERENCES);
			if(error != null)
				this.eventDispatcher.addEventListener(PutIoEvent.PUT_IO_ERROR_EVENT, error, false, 0, USE_WEAK_REFERENCES);
		}		
		
		public function get id():String
		{
			return _id;
		}
		
		public function set id(value:String):void
		{
			_id = value;
		}		
		
		/**
		 * Function called if the communication has terminated properly.
		 * This does not mean that Put.io did not return an error.
		 * 
		 * For example, if the API Key is not the good one, this function will be still called
		 */
		private function onResult(evt:Event):void
		{
			//Extract the result
			var urlLoader:URLLoader = evt.target as URLLoader;
			//Converting to an object
			var data:Object = convertJsonStringToObject(urlLoader.data);			
			//Get error variable
			var error:Boolean = data.error as Boolean;
			
			//If error flag is true, the request has sent a wrong parameters
			if(error) {
				var putIoError:PutIoErrorEvent = new PutIoErrorEvent(evt.bubbles, evt.cancelable);
				putIoError.id = data.id;
				putIoError.userName = data.user_name;
				putIoError.loaderId = this.id;
				
				putIoError.errorMessage = data.error_message;
				
				this.eventDispatcher.dispatchEvent(putIoError);
			}
			//Otherwise, the request was good
			else {
				var putIoResult:PutIoResultEvent = new PutIoResultEvent(evt.bubbles, evt.cancelable);
				putIoResult.id = data.id;
				putIoResult.userName = data.user_name;
				putIoResult.loaderId = this.id;
				
				putIoResult.nbResults = data.response.total;
				putIoResult.results = data.response.results;
				
				this.eventDispatcher.dispatchEvent(putIoResult);
			}			
		}
		
		/**
		 * Function called if the communication has not terminated properly.
		 * This mean that a connection problem happenned during the request
		 * 
		 */			
		private function onIoError(evt:IOErrorEvent):void
		{
			var putIoError:PutIoErrorEvent = new PutIoErrorEvent(evt.bubbles, evt.cancelable);
			putIoError.errorMessage = evt.text;
			
			this.eventDispatcher.dispatchEvent(putIoError);					
		}
	
		/**
		 * Function called if the communication has not terminated properly.
		 * This mean that a connection problem happenned during the request
		 * 
		 * It could be "Sandbox violation" if the crossdomain.xml file cannot be loaded
		 */			
		private function onSecurityError(evt:SecurityErrorEvent):void
		{
			var putIoError:PutIoErrorEvent = new PutIoErrorEvent(evt.bubbles, evt.cancelable);
			putIoError.errorMessage = evt.text;
			
			this.eventDispatcher.dispatchEvent(putIoError);			
		}
		
		/**
		 * Construct URL for requests
		 * 
		 * Example : http://api.put.io/v1/files?method=list
		 * Base URL : http://api.put.io/v1/
		 * Category : files
		 * END URL : ?method=
		 * Method : list
		 */		
		private function getUrl(_category:String, _method:String):String
		{
			return PUT_DOT_IO_API_URL+_category+END_URL+_method;	
		}
		
		private function getUrlRequestInstance(_url:String):URLRequest
		{
			var request:URLRequest = new URLRequest(_url);
			
			//Default configuration
			request.method = URLRequestMethod.GET;
			request.requestHeaders.push(new URLRequestHeader("content-type", "text/plain"));
			//request.requestHeaders.push(new URLRequestHeader("pragma", "no-cache"));
			
			return request;
		}			
		
		private function getUrlLoaderInstance():URLLoader
		{
			var loader:URLLoader = new URLLoader();
			loader.addEventListener(Event.COMPLETE, onResult);
			loader.addEventListener(IOErrorEvent.IO_ERROR, onIoError);
			loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
			
			return loader;			
		}				
		
		private function getParameters(_parameters:Object):URLVariables
		{
			//Build parameters by adding api key and secret and request parameters
			var params:Object = new Object();
			params.api_key = PutIoLibSettings.getInstance().apiKey;
			params.api_secret = PutIoLibSettings.getInstance().apiSecret;
			
			//If params is null server doesn't accept the request
			if(_parameters != null)
				params.params = _parameters;
			else
				params.params = new Object();
			
			//Encoding parameters to a JSON String
			var encodedParams:String = convertToJsonString(params);
			
			//Encapsulate JSON data in request variable
			//Result could be http://api.put.io/v1/transfers?method=list&request={ ... JSON DATA ...}
			var urlVariables:URLVariables = new URLVariables();
			urlVariables.request = encodedParams;			
			
			return urlVariables;
		}		
		
		private function convertToJsonString(_objectToConvert:Object):String
		{
			return JSON.encode(_objectToConvert);
		}
		
		private function convertJsonStringToObject(_jsonString:String):Object
		{
			return JSON.decode(_jsonString);
		}
		
		private function getApiKey():String
		{
			return PutIoLibSettings.getInstance().apiKey;
		}
		
		private function getApiSecret():String
		{
			return PutIoLibSettings.getInstance().apiSecret;	
		}			
	}
}